package log

import (
	"os"
	"path"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var _ Logger = (*YLogger)(nil)

type YLogger struct {
	l *zap.SugaredLogger
}

func (z *YLogger) Debug(i ...interface{}) {
	z.l.Debug(i...)
}

func (z *YLogger) Info(i ...interface{}) {
	z.l.Info(i...)
}

func (z *YLogger) Warn(i ...interface{}) {
	z.l.Warn(i...)
}

func (z *YLogger) Error(i ...interface{}) {
	z.l.Error(i...)
}

func (z *YLogger) Fatal(i ...interface{}) {
	z.l.Fatal(i...)
}

func (z *YLogger) Panic(i ...interface{}) {
	z.l.Panic(i...)
}

func (z *YLogger) Debugf(s string, i ...interface{}) {
	z.l.Debugf(s, i...)
}

func (z *YLogger) Infof(s string, i ...interface{}) {
	z.l.Infof(s, i...)
}

func (z *YLogger) Warnf(s string, i ...interface{}) {
	z.l.Warnf(s, i...)
}

func (z *YLogger) Errorf(s string, i ...interface{}) {
	z.l.Errorf(s, i...)
}

func (z *YLogger) Fatalf(s string, i ...interface{}) {
	z.l.Fatalf(s, i...)
}

func (z *YLogger) Panicf(s string, i ...interface{}) {
	z.l.Panicf(s, i...)
}

func (z *YLogger) Debugw(s string, m map[string]interface{}) {
	z.l.Debugw(s, map2slice(m)...)
}

func (z *YLogger) Infow(s string, m map[string]interface{}) {
	z.l.Infow(s, map2slice(m)...)
}

func (z *YLogger) Warnw(s string, m map[string]interface{}) {
	z.l.Warnw(s, map2slice(m)...)
}

func (z *YLogger) Errorw(s string, m map[string]interface{}) {
	z.l.Errorw(s, map2slice(m)...)
}

func (z *YLogger) Fatalw(s string, m map[string]interface{}) {
	z.l.Fatalw(s, map2slice(m)...)
}

func (z *YLogger) Panicw(s string, m map[string]interface{}) {
	z.l.Panicw(s, map2slice(m)...)
}

func (z *YLogger) Log(level Level, i ...interface{}) {
	switch level {
	case LevelInfo:
		z.l.Info(i...)
	case LevelDebug:
		z.l.Debug(i...)
	case LevelWarn:
		z.l.Warn(i...)
	case LevelError:
		z.l.Error(i...)
	case LevelPanic:
		z.l.Panic(i...)
	case LevelFatal:
		z.l.Fatal(i...)
	}
}

func (z *YLogger) Logf(level Level, s string, i ...interface{}) {
	switch level {
	case LevelInfo:
		z.l.Infof(s, i...)
	case LevelDebug:
		z.l.Debugf(s, i...)
	case LevelWarn:
		z.l.Warnf(s, i...)
	case LevelError:
		z.l.Errorf(s, i...)
	case LevelPanic:
		z.l.Panicf(s, i...)
	case LevelFatal:
		z.l.Fatalf(s, i...)
	}
}

func (z *YLogger) Logw(level Level, s string, m map[string]interface{}) {
	switch level {
	case LevelInfo:
		z.l.Infow(s, map2slice(m)...)
	case LevelDebug:
		z.l.Debugw(s, map2slice(m)...)
	case LevelWarn:
		z.l.Warnw(s, map2slice(m)...)
	case LevelError:
		z.l.Errorw(s, map2slice(m)...)
	case LevelPanic:
		z.l.Panicw(s, map2slice(m)...)
	case LevelFatal:
		z.l.Fatalw(s, map2slice(m)...)
	}
}

func (z *YLogger) Sync() {
	z.l.Sync()
}

func NewLogger(option ...Option) Logger {
	c := &config{
		logDir:       "./log",
		maxAge:       30,
		rotationSize: 100,
		level:        LevelInfo,
		callerSkip:   1,
	}
	for _, v := range option {
		v(c)
	}
	core := zapcore.NewCore(getEncoder(), getWriteSyncer(c), zap.NewAtomicLevelAt(level2zapLevel(c.level)))
	if c.callerSkip == -1 {
		return &YLogger{
			l: zap.New(core).Sugar(),
		}
	}
	return &YLogger{
		l: zap.New(core, zap.AddCaller(), zap.AddCallerSkip(c.callerSkip)).Sugar(),
	}
}

func getEncoder() zapcore.Encoder {
	conf := zap.NewProductionEncoderConfig()
	conf.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
	return zapcore.NewJSONEncoder(conf)
}

func getWriteSyncer(c *config) zapcore.WriteSyncer {
	if c.stdFile {
		log := &lumberjack.Logger{
			Filename: path.Join(c.logDir, "log.log"),
			MaxSize:  c.rotationSize,
			MaxAge:   c.maxAge,
		}
		return zapcore.NewMultiWriteSyncer(zapcore.AddSync(log))
	}
	return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout))
}

func level2zapLevel(level Level) zapcore.Level {
	switch level {
	case LevelInfo:
		return zapcore.InfoLevel
	case LevelDebug:
		return zapcore.DebugLevel
	case LevelWarn:
		return zapcore.WarnLevel
	case LevelError:
		return zapcore.ErrorLevel
	case LevelPanic:
		return zapcore.PanicLevel
	case LevelFatal:
		return zapcore.FatalLevel
	}
	return zapcore.InfoLevel
}

func map2slice(m map[string]interface{}) []interface{} {
	re := make([]interface{}, 0, len(m)*2)
	for k, v := range m {
		re = append(re, k, v)
	}
	return re
}
